// 手动实现一个promise
// promise 实现原理
// 1. promise  接受一个回调任务，在new promise(executor)的时候会立即执行回调
// 2. executor 内部的异步任务被放入到宏/微任务队列等待执行
// 3. then/catche 手机相应成功或者失败的回调
// 4. executor 异步任务得到结果后被执行，触发resolve、reject从成功或者失败队列中依次执行
// 学习文章
// https://blog.csdn.net/ww2727/article/details/127001199


// Promise/A+规范的三种状态
const PENDING = 'pending'     // （等待态）
const FULFILLED = 'fulfilled' // （执行态）
const REJECTED = 'rejected'   // （拒绝态）
// 以上状态不可逆
class Npromise {
    constructor(executor) {
        // 异步成功的时候的队列
        this._resolveQueue = []
        // 异步失败的时候的队列
        this._rejectQueue = []
        // 当前promise的状态 默认空闲
        this._status = PENDING
        // 储存then回调return的值
        this._value = undefined

        // executor 接受两个参数 resolve 和 reject 分别是抛出成功、失败的回调信息
        let _resolve = (val) => {
            const resRun = () => {
                // 成功的回调
                if (this._status !== PENDING) return
                this._status = FULFILLED
                this._value = val  // 存储当前的value 在最后面状态码调用

                while (this._resolveQueue.length) {
                    // 移除第一位并返回
                    const callback = this._resolveQueue.shift()
                    callback(val)
                }
            }
            // 避免是同步任务
            setTimeout(resRun)
        }

        // 实现同resolve
        let _reject = (val) => {
            const rejRun = () => {
                // 成功的回调
                if (this._status !== PENDING) return
                this._status = REJECTED
                this._value = val  // 存储当前的value 在最后面状态码调用

                while (this._rejectQueue.length) {
                    const callback = this._rejectQueue.shift()
                    callback(val)
                }
            }

            setTimeout(rejRun)
        }

        // new Promise 的时候立马执行回调
        executor(_resolve, _reject)
    }

    // then方法,接收一个成功的回调和一个失败的回调，并push进对应队列
    then(resolveFn, rejectFn) {
        //  resolveFn = (结果)=> {}

        // 值穿透 & 状态已变更的情况
        // 值穿透  根据规范，如果then()接收的参数不是function，那么我们应该忽略它。
        //        如果没有忽略，当then()回调不是function会抛出异常导致链式调用中断
        if (typeof resolveFn !== 'function') {
            // 直接写一个方法
            resolveFn = value => value
        }

        if (typeof rejectFn !== 'function') {
            rejectFn = reason => {
                throw new Error(reason instanceof Error ? reason.message : reason);
            }
        }

        // then 需要支持链式调用，也就是可以一直 .then().then().then().then().then()
        // 如果需要这样处理 也就是说 then需要返回一个 promise
        return new Npromise((resolve, reject) => {
            // 这也就是说 为什么调用者必须使用 resolve() 或者 reject() 以后 then才有用
            // 因为 then 只是加入了一个队列，resolve 才是执行
            const fulfilledFn = value => {
                try {
                    // 执行第一个（当前的）promise的成功回调，并获取返回值
                    let result = resolveFn(value)
                    // 判断结果 如果结果是promise, 那么等待promise状态变更，否则直接resolve
                    if (result instanceof Npromise) {
                        result.then(resolve, reject)
                    } else {
                        resolve(result)
                    }
                } catch (error) {
                    reject(error)
                }
            }

            // reject同理
            const rejectedFn = error => {
                try {
                    let x = rejectFn(error)
                    x instanceof Npromise ? x.then(resolve, reject) : resolve(x)
                } catch (error) {
                    reject(error)
                }
            }

            switch (this._status) {
                // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
                case PENDING:
                    this._resolveQueue.push(fulfilledFn)
                    this._rejectQueue.push(rejectedFn)
                    break;
                // 当状态已经变为resolve/reject时,直接执行then回调
                case FULFILLED:
                    fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
                    break;
                case REJECTED:
                    rejectedFn(this._value)
                    break;
            }
        })
    }

    // 失败的时候执行 但是不能影响链式
    catch(rejectFun) {
        // 必须 return
        return this.then(null, rejectFun)
    }

    // 无论成功还是失败 都执行
    finally(callback) {
        // 把结果执行后使他可以继续then或者catch下去
        // 为什么要 this.then(resolve, reject) 参数都传递，因为可以链式的时候，使用then 和 catch的时候 都可以拿到值
        return this.then(
            value => Npromise.resolve(callback()).then(() => value),
            reason => Npromise.reject(callback()).then(() => reason)
        )
    }

    // resolve 返回一个给定值解析后的primise对象 
    static resolve(value) {
        // 根据规范, 如果参数是Promise实例, 直接return这个实例 以便于后面可以继续链式操作
        if (value instanceof Npromise) return value
        // 如果是其他类型 就返回一个promise 
        return new Npromise(resolve => resolve(value))
        // 调用方式如下
        // Npromise.resolve().then(x => x)
    }

    // reject 返回一个带有拒绝原因的primise对象
    static reject(reason) {
        // 拒绝时，传递的参数一定不是promise对象 所以不用判断
        return new Npromise((resolve, reject) => { reject(reason) })

        // 调用方式如下
        // Npromise.reject({code: -999, msg: 'error'}).catch(x => x)
    }

    // all 所有的实例一起执行 参数为promise数组
    // Npromise.all([promise1, promise2,....primise])
    // 此实例在 iterable 参数内所有的 promise 都完成（resolved）的情况下 回调完成resolve
    // 如果有一个primise失败（rejected） 的情况下 此示例回调失败（reject）
    static all(promiseArr) {
        let index = 0
        const result = []
        return new Npromise((resolve, reject) => {
            let len = promiseArr.length
            // Npromise.resolve() 进行了判断处理，如果不是primise对象会对其进行封装成primise
            promiseArr.forEach((p, i) => {
                Npromise.resolve(p).then(
                    val => {
                        // primise执行成功了
                        index++
                        result[i] = val
                        //所有then执行后, resolve结果
                        if (index === len - 1) {
                            // 如果执行到最后一个promise的then表示全部正常
                            resolve(result)
                        }
                    },
                    err => {
                        // 有一个错误的primise，直接中断返回
                        reject(err)
                    }
                )
            })

        })
    }
}

const Por = new Npromise((resolve, reject) => {
    setTimeout(() => {
        resolve(new Error('err'))
    }, 1000)
})

// Por.then(res => {
//     console.log('res ========>1 ', res)
//     return 1
// }).then(res => {
//     console.log('res ========>2 ', res)
//     return 2
// }).then(res => {
//     console.log('res ========>3 ', res)
//     return 3
// }).then(res => {
//     console.log('res ========>4 ', res)
//     return 4
// })

Npromise.all([1, 2, 3, Npromise.reject('error'), 5, 56, 6, 7]).then(res => {
    console.log('全部执行完毕 ===> ', res)
}, err => {
    console.log('出错了 ===>  ', err)
})